namespace Daemon.Service.Jwt
{
    using Daemon.Service.Contract.Jwt;
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Caching.Distributed;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Primitives;
    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.Collections.Generic;
    using System.IdentityModel.Tokens.Jwt;
    using System.Linq;
    using System.Security.Claims;
    using System.Text;
    using System.Threading.Tasks;
    using Daemon.Common.Cache;
    using Daemon.Model.Jwt;
    using Microsoft.AspNetCore.Mvc;
    using Daemon.Common;
    using Daemon.Model.ViewModel;
    using Daemon.Model;
    using System.Security.Cryptography;
    using IdentityModel;
    using Daemon.Common.Auth;
    using Microsoft.Extensions.Options;
    using Daemon.Middleware;
    using Daemon.Repository.Contract;

    public class JwtAppService : IJwtAppService
    {
        #region Initialize

        private const string RefreshTokenIdClaimType = "refresh_token_id";

        private readonly JwtBearerOptions _jwtBearerOptions;

        /// <summary>
        /// 
        /// </summary>
        private static ISet<JwtAuthorizationDto> _tokens = new HashSet<JwtAuthorizationDto>();

        /// <summary>
        /// 
        /// </summary>
        private readonly IDaemonDistributedCache _cache;

        /// <summary>
        /// 
        /// </summary>
        private readonly IConfiguration _configuration;

        /// <summary>
        ///
        /// </summary>
        private readonly IHttpContextAccessor _httpContextAccessor;

        private readonly JwtOptions _jwtOptions;

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="cache"></param>
        /// <param name="httpContextAccessor"></param>
        /// <param name="configuration"></param>
        public JwtAppService(IDaemonDistributedCache cache, IOptionsSnapshot<JwtBearerOptions> jwtBearerOptions, IOptionsSnapshot<JwtOptions> jwtOptions,
            IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
        {
            _cache = cache;
            _httpContextAccessor = httpContextAccessor;
            _jwtBearerOptions = jwtBearerOptions.Get(JwtBearerDefaults.AuthenticationScheme);
            _configuration = configuration;
            _jwtOptions = jwtOptions.Value;
        }

        #endregion

        #region API Implements
        public async Task<JwtAuthorizationDto> CreateAuthTokenAsync(BaseUser user, List<Claim> extraClaims = null)
        {
            var result = new JwtAuthorizationDto();
            var (refreshTokenId, refreshToken) = await CreateRefreshTokenAsync(user.Id.ToString());
            result.RefreshToken = refreshToken;
            result.Token = CreateJwtToken(user, refreshTokenId, extraClaims);

            return result;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="token">Token</param>
        /// <returns></returns>
        public async Task DeactivateAsync(string token)
        => await _cache.SetStringAsync(GetKey(token),
                " ", new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow =
                        TimeSpan.FromMinutes(Convert.ToDouble(_configuration["Jwt:ExpireMinutes"]))
                });

        public async Task DeactivateCurrentAsync()
        => await DeactivateAsync(GetCurrentAsync());

        public async Task<bool> IsActiveAsync(string token)
        {
            var result = await _cache.GetAsync(GetKey(token)) == null;
            return result;
        }

        public async Task<bool> IsCurrentActiveTokenAsync()
        => await IsActiveAsync(GetCurrentAsync());

        public async Task<JwtAuthorizationDto> RefreshAsync(string token, BaseUser dto)
        {
            var jwtOld = GetExistenceToken(token);
            if (jwtOld == null)
            {
                return new JwtAuthorizationDto()
                {
                    Token = "",
                    Success = false
                };
            }

            var jwt = await CreateAuthTokenAsync(dto);

            await DeactivateCurrentAsync();

            return jwt;
        }

        #endregion

        #region Method
        private async Task<(string refreshTokenId, string refreshToken)> CreateRefreshTokenAsync(string userId)
        {
            var tokenId = Guid.NewGuid().ToString("N");

            var rnBytes = new byte[32];
            using var rng = RandomNumberGenerator.Create();
            rng.GetBytes(rnBytes);
            var token = Convert.ToBase64String(rnBytes);

            var options = new DistributedCacheEntryOptions();
            options.SetAbsoluteExpiration(TimeSpan.FromDays(_jwtOptions.RefreshTokenExpiresDays));

            await _cache.SetStringAsync(GetRefreshTokenKey(userId, tokenId), token, options);

            return (tokenId, token);
        }

        private string CreateJwtToken(BaseUser user, string refreshTokenId, List<Claim> extraClaims = null)
        {
            if (user is null) throw new ArgumentNullException(nameof(user));
            if (string.IsNullOrEmpty(refreshTokenId)) throw new ArgumentNullException(nameof(refreshTokenId));

            var defaultClaims = new List<Claim>
            {
                new Claim(JwtClaimTypes.Id, user.Id.ToString()),
                new Claim(JwtClaimTypes.Name, user.UserName),
                new Claim(RefreshTokenIdClaimType, refreshTokenId)
            };
            if (extraClaims != null)
            {
                defaultClaims.AddRange(extraClaims);
            }

            var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);
            identity.AddClaims(defaultClaims);
            if (extraClaims != null)
            {
                identity.AddClaims(extraClaims);
            }
            _httpContextAccessor.HttpContext.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = identity,
                Issuer = _jwtBearerOptions.TokenValidationParameters.ValidIssuer,
                Audience = _jwtBearerOptions.TokenValidationParameters.ValidAudience,
                Expires = DateTime.UtcNow.AddMinutes(_jwtOptions.AccessTokenExpiresMinutes),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")), SecurityAlgorithms.HmacSha256)
            };

            var handler = _jwtBearerOptions.SecurityTokenValidators.OfType<JwtSecurityTokenHandler>().FirstOrDefault()
                ?? new JwtSecurityTokenHandler();
            var securityToken = handler.CreateJwtSecurityToken(tokenDescriptor);
            var token = handler.WriteToken(securityToken);

            return token;
        }

        private string GetRefreshTokenKey(string userId, string refreshTokenId)
        {
            if (string.IsNullOrEmpty(userId)) throw new ArgumentNullException(nameof(userId));
            if (string.IsNullOrEmpty(refreshTokenId)) throw new ArgumentNullException(nameof(refreshTokenId));

            return $"{userId}:{refreshTokenId}";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="token">Token</param>
        /// <returns></returns>
        private static string GetKey(string token)
            => $"deactivated token:{token}";

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private string GetCurrentAsync()
        {
            //http header
            var authorizationHeader = _httpContextAccessor
                .HttpContext.Request.Headers["authorization"];

            //token
            return authorizationHeader == StringValues.Empty
                ? string.Empty
                : authorizationHeader.Single().Split(" ").Last();// bearer tokenvalue
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="token">Token</param>
        /// <returns></returns>
        private JwtAuthorizationDto GetExistenceToken(string token)
            => _tokens.SingleOrDefault(x => x.Token == token);

        #endregion
    }
}
